package org.mcflier.appuntamento.io.driver.imdb;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.mcflier.appuntamento.core.exception.PropertiesException;
import org.mcflier.appuntamento.core.logger.LoggerManager;
import org.mcflier.appuntamento.data.model.AlternativeTitle;
import org.mcflier.appuntamento.data.model.Genre;
import org.mcflier.appuntamento.data.model.Movie;
import org.mcflier.appuntamento.data.model.Production;
import org.mcflier.appuntamento.i18n.Country;
import org.mcflier.appuntamento.i18n.I18N;
import org.mcflier.appuntamento.i18n.exception.I18NCountryNotFoundException;
import org.mcflier.appuntamento.io.exception.MovieNotFoundException;
import org.mcflier.appuntamento.io.exception.ParsingException;
import org.mcflier.appuntamento.io.exception.RemoteConnectionException;
import org.mcflier.appuntamento.io.exception.RemoteResourceNotFoundException;

public class TheMovieDBDriver extends AbstractIMDBDriver {
	
	private String mainUrl, apiKey;
	private Logger logger;
	private I18N i18n;
	
	public TheMovieDBDriver() throws PropertiesException {
		super();
		this.logger = LoggerManager.getInstance().getLogger(this.getClass().getName());
		this.i18n = I18N.getInstance();
	}

	@Override
	public void init(Properties properties) throws PropertiesException {
		super.init(properties);
		
		this.mainUrl = this.getProperties().getProperty(DRIVER_MAIN_URL);
		this.apiKey = this.getProperties().getProperty(DRIVER_API_KEY);
		
		if(mainUrl == null || apiKey == null)
			throw new PropertiesException(this.getClass().getCanonicalName()+": Key properties not found");
		if(mainUrl.trim().equals("") || apiKey.trim().equals(""))
			throw new PropertiesException(this.getClass().getCanonicalName()+": Empty properties");
		
		this.logger.info("Loaded main url: "+mainUrl);
		this.logger.info("Loaded api key");
	}
	
	private String getJSONFile(String petitionUrl) throws RemoteConnectionException, FileNotFoundException{
		try {
			HttpURLConnection connection = null;
		    URL url;
			url = new URL(petitionUrl);
			long millis = System.currentTimeMillis();
			connection = (HttpURLConnection) url.openConnection();
		    connection.connect();
		    this.logger.info("Connection opened: "+connection);
		    BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
		    String temp = "", file = "";
			while(temp != null){
				temp = reader.readLine();
				file+=(temp==null)?"":(temp+"\n");
			}
			reader.close();
			connection.disconnect();
			this.logger.info(System.currentTimeMillis()-millis+"ms Connection finished: "+connection);
			
			return file;
		} catch(FileNotFoundException e){
			throw e;
		} catch (UnknownHostException e){
			throw new RemoteConnectionException("Check internet connection", e);
		} catch (MalformedURLException e) {
			throw new RemoteConnectionException(e);
		} catch (IOException e) {
			throw new RemoteConnectionException(e);
		}
	}

	@Override
	public Movie getMovie(String id) throws ParsingException, MovieNotFoundException, RemoteConnectionException {
		try {
			Movie movie = null;
			JSONParser parser = new JSONParser();
			JSONObject root = null;
			JSONArray arrayAux = null;
			JSONObject objectAux = null;
			
			StringBuilder sUrl = new StringBuilder(this.mainUrl);
			if(sUrl.charAt(sUrl.length()-1)!='/') sUrl.append("/");
			sUrl.append(DRIVER_MOVIE_TAG)
			.append("/")
			.append(id)
			.append("?")
			.append(DRIVER_API_KEY)
			.append("=")
			.append(this.apiKey)
			.append("&")
			.append(DRIVER_LANGUAGE_TAG)
			.append("=")
			.append(this.i18n.getAppLanguage().getCode());
			
			root = (JSONObject) parser.parse(this.getJSONFile(sUrl.toString()));
			movie = new Movie(
					(String)root.get(IMDB_ID_TAG),
					(String)root.get(ORIGINAL_TITLE_TAG)
					);

			movie.setHomepage((String)root.get(HOMEPAGE_TAG));
			movie.setOverview((String)root.get(OVERVIEW_TAG));
			movie.setPosterPath((String)root.get(POSTER_PATH_TAG));
			try {
				movie.setReleaseDate(Timestamp.valueOf((root.get(RELEASE_DATE_TAG)==null || String.valueOf(root.get(RELEASE_DATE_TAG)).equals(""))?null:(String.valueOf(root.get(RELEASE_DATE_TAG))+" 00:00:00")));
			} catch (Exception e1) {
				movie.setReleaseDate(null);
				this.logger.warning("Release date for movie '"+movie+"' is set to null because of malformed date");
			}
			String runtime = String.valueOf(root.get(RUNTIME_TAG));
			movie.setRuntime(Integer.parseInt((runtime.equals("null"))?"0":runtime));
			movie.setTagline((String)root.get(TAGLINE_TAG));
			movie.setAlternativeName(new AlternativeTitle(this.i18n.getAppLanguage(), (String)root.get(ALTERNATIVE_TITLE_TAG)));

			//Genres (i18n)
			arrayAux = (JSONArray)root.get(GENRES_TAG);
			if(arrayAux!=null){
				Genre genre = null;
				for (Object object : arrayAux) {
					objectAux = (JSONObject)object;
					genre = new Genre(
							Integer.parseInt(String.valueOf(objectAux.get(GENRES_ID_TAG))),
							(String)objectAux.get(GENRES_NAME_TAG)
							);

					movie.addGenre(genre);
				}
			}

			//Production companies
			arrayAux = (JSONArray)root.get(PRODUCTION_COMPANIES_TAG);
			if(arrayAux!=null){
				Production production = null;
				for (Object object : arrayAux) {
					objectAux = (JSONObject)object;
					production = new Production(
							Integer.parseInt(String.valueOf(objectAux.get(PRODUCTION_COMPANIES_ID_TAG))),
							(String)objectAux.get(PRODUCTION_COMPANIES_NAME_TAG)
							);

					movie.addProduction(production);
				}
			}

			//Production countries
			arrayAux = (JSONArray)root.get(PRODUCTION_COUNTRIES_TAG);
			if(arrayAux!=null){
				Country country = null;
				for (Object object : arrayAux) {
					objectAux = (JSONObject)object;
					String cCtry="";
					try {
						cCtry = String.valueOf(objectAux.get(PRODUCTION_COUNTRIES_ID_TAG));
						country = this.i18n.createCountry(cCtry);
						//Country name is not read from file, it's obtained from i18n
					} catch (I18NCountryNotFoundException e) {
						this.logger.warning("Country code: "+cCtry+" not found. Used values retrieved from IMDB");
						country = new Country(cCtry, String.valueOf(objectAux.get(PRODUCTION_COUNTRIES_NAME_TAG)));
					}
					
					movie.addProductionCountry(country);
				}
			}
			
			this.logger.info("Movie readed: "+movie);
			return movie;
		} catch (ParseException e) {
			throw new ParsingException(e);
		} catch (FileNotFoundException e1) {
			throw new MovieNotFoundException(id);
		} catch(NullPointerException e){
			this.logger.warning("Title or imdb id null trying to get the movie: "+id+"\n"+e.getMessage());
			throw new MovieNotFoundException(e);
		}
	}

	@Override
	public List<Movie> getMovies(List<String> ids) throws ParsingException, RemoteConnectionException {
		List<Movie> movies = new ArrayList<Movie>();
		for (String id : ids) {
			try {
				movies.add(this.getMovie(id));
			} catch (MovieNotFoundException e) {
				this.logger.warning("Movie not found: "+e.getMessage());
			}
		}
		
		return movies;
	}

	@Override
	public List<Movie> getShowtimes(Country country, String zipCode) throws ParsingException, RemoteResourceNotFoundException, PropertiesException, RemoteConnectionException {
		String yql, imdb_url;
		List<String> ids = new ArrayList<String>();

		yql = this.getProperties().getProperty(SHOWTIMES_YQL_URL_TAG);
		if(yql == null)
			throw new PropertiesException(this.getClass()+" Property "+SHOWTIMES_YQL_URL_TAG+" not found");

		imdb_url = this.getProperties().getProperty(IMDB_URL_TAG);
		if(imdb_url == null)
			throw new PropertiesException(this.getClass()+" Property "+IMDB_URL_TAG+" not found");

		StringBuilder sBuilder = new StringBuilder(yql);
		sBuilder.append("?")
		.append(this.getProperties().get(FORMAT_TAG)).append("=").append(this.getProperties().getProperty(FORMAT))
		.append("&")
		.append(this.getProperties().getProperty(MOVIE_URL_TAG)).append("=").append(imdb_url).append((imdb_url.endsWith("/"))?"":"/")
		.append(country.getCode()).append("/").append(zipCode).append("?")
		.append(this.getProperties().getProperty(SORT_TAG)).append("=").append(this.getProperties().getProperty(SORT_METHOD_TAG))
		.append("&")
		.append(this.getProperties().getProperty(SORT_ORIENTATION_TAG)).append("=").append(this.getProperties().getProperty(SORT_ORIENTATION));
		
		try{
			JSONParser parser = new JSONParser();
			JSONObject root = null;
			JSONArray arrayAux = null;
			JSONObject objectAux = null;

			root = (JSONObject) parser.parse(this.getJSONFile(sBuilder.toString()));
			objectAux = (JSONObject)root.get("query");
			objectAux = (JSONObject)objectAux.get("results");
			arrayAux = (JSONArray)objectAux.get("a");
			String href=null;
			for (Object object : arrayAux) {
				objectAux = (JSONObject)object;
				href = String.valueOf(objectAux.get("href")).split("/")[3];
				ids.add(href);
				this.logger.info("Imdb id recovered: "+href);
			}

			return this.getMovies(ids);
		} catch (ParseException e) {
			throw new ParsingException(e);
		} catch (FileNotFoundException e) {
			throw new RemoteResourceNotFoundException("Resource: "+sBuilder.toString()+"\nThis resource cannot be found", e);
		}
	}

	@Override
	public List<Movie> getNowPlaying() throws ParsingException, RemoteConnectionException, RemoteResourceNotFoundException {
		int i = 1;
		List<Movie> movies = new ArrayList<Movie>();
		List<Movie> tempMovies;
		do{
			tempMovies = this.getNowPlaying(i++);
			for (Movie movie : tempMovies) {
				movies.add(movie);
			}
		}
		while(tempMovies!=null && tempMovies.size()>0);
		
		return movies;
	}

	@Override
	public List<Movie> getNowPlaying(int page) throws ParsingException, RemoteConnectionException, RemoteResourceNotFoundException {
		this.logger.info("Retrieving now playing movies. Page: "+page);
		JSONParser parser = new JSONParser();
		JSONObject root = null;
		JSONArray arrayAux = null;
		JSONObject objectAux = null;
		
		StringBuilder sUrl = new StringBuilder(this.mainUrl);
		sUrl.append((this.mainUrl.endsWith("/"))?"":"/").append(NOW_PLAYING_SUFFIX_TAG).append("?")
		.append(DRIVER_API_KEY).append("=").append(this.apiKey)
		.append("&")
		.append(DRIVER_LANGUAGE_TAG).append("=").append(this.i18n.getAppLanguage().getCode())
		.append("&")
		.append(DRIVER_PAGE_TAG).append("=").append(page);
		
		try {
			root = (JSONObject) parser.parse(this.getJSONFile(sUrl.toString()));
			arrayAux = (JSONArray)root.get("results");
			List<String> ids = new ArrayList<String>();
			for (Object object : arrayAux) {
				objectAux = (JSONObject)object;
				ids.add(String.valueOf(objectAux.get("id")));
				this.logger.info("Id: "+String.valueOf(objectAux.get("id"))+" "+String.valueOf(objectAux.get("title")));
			}
			
			return this.getMovies(ids);
		} catch (FileNotFoundException e) {
			throw new RemoteResourceNotFoundException("Resource "+sUrl+"\nThis remote resource is not available", e);
		} catch (ParseException e) {
			throw new ParsingException(e);
		}
	}
}